import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class UDPClient {
	private static HashMap<String, ArrayList<PSPEvent>> channel_map;
	public static void main(String args[]) throws Exception{
		BufferedReader inFromUser = new BufferedReader (new InputStreamReader(System.in));
		DatagramSocket clientSocket = new DatagramSocket();
		// Set a default Timeout for the socket.  If no response is received by then,
		// throws an exception		
		String client_ID = "ClientA";
		InetAddress IPAddress = InetAddress.getByName("localhost");
		String sentence = "";		
		Date date = new Date();
		channel_map = new HashMap<String, ArrayList<PSPEvent>>();
		int packet_counter = 0;
		
		
		
		boolean stay_connected = true;
		while (stay_connected){
			byte[] sendData = new byte[Globals.packetSize];
			byte[] receiveData = new byte[Globals.packetSize];
			PSPAction packet_action = null;
			PSPPubSchedule packet_schedule = null;
			String packet_channel = null;
			int packet_ID = 0;			
			String packet_command = "";
			
			do{
				System.out.print("Ready to send: ");
				sentence = inFromUser.readLine();
			}while (sentence.isEmpty());
			
			ArrayList<String> command_arr = parseInput(sentence);
				
			if (command_arr.get(0).equalsIgnoreCase("quit")){
				stay_connected = false;				
			}
			else if (command_arr.get(0).equalsIgnoreCase("SET")){
				if (command_arr.get(1).isEmpty()){
					System.out.println("No channel name provided");
					continue;
				}
					
				// Use the following channel
				packet_action = PSPAction.CLIENT_SET_CHANNEL;
				packet_ID = 0;
				packet_channel = command_arr.get(1);
				try{
					// Try to get the publication schedule base on the packet string sent
					packet_schedule = PSPPubSchedule.valueOf(command_arr.get(2));
				}catch (Exception e){
					// If it fails, just default to Immediate
					packet_schedule = PSPPubSchedule.IMMEDIATELY_UPON_PUBLISHING;
				}
				// There is no additional data sent with a SET request
				packet_command = "";				
			}
			else if (command_arr.get(0).equalsIgnoreCase("PUB")){
				packet_action = PSPAction.CLIENT_PUBLISH_EVENT;
				packet_ID = 0;
				packet_channel = command_arr.get(1);
				try{
					// Try to get the publication schedule base on the packet string sent
					packet_schedule = PSPPubSchedule.IMMEDIATELY_UPON_PUBLISHING;
				}catch (Exception e){
					// If it fails, just default to Channel
					packet_schedule = PSPPubSchedule.CHANNEL_DEFAULT;
				}
				packet_command = command_arr.get(2).replace('\"', ' ').trim();
				
			}
			else if (command_arr.get(0).equalsIgnoreCase("SUB")){
				packet_action = PSPAction.CLIENT_SUBSCRIBE_CHANNEL;
				packet_ID = 0;
				packet_channel = command_arr.get(1);
				try{
					// Set the publication schedule to immediate
					packet_schedule = PSPPubSchedule.IMMEDIATELY_UPON_PUBLISHING;
				}catch (Exception e){
					// If it fails, just default to Channel
					packet_schedule = PSPPubSchedule.CHANNEL_DEFAULT;
				}
				packet_command = "";
			}
			else
				continue;

			sendData = PSPPacket.create_packet(packet_action, client_ID, packet_ID, packet_channel, packet_schedule, packet_command);
		
			DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, 9876);
			clientSocket.send(sendPacket);
			
			DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
			clientSocket.receive(receivePacket);
			
			
			String[] received_packet = decodePacket(receivePacket);
			
			// If packet is somehow corrupt/invalid, then exit.
			if (received_packet == null){
				// Bad packet - do not send response.  Server will resend packet later.				
			}
			else{
				// If the packet was received but not processed, display error
				if (PSPAction.valueOf(received_packet[0]) == PSPAction.SERVER_RESPONSE_NEG){
					System.out.println("Request invalid.  Please re-send prior request.");
				}
				// If everything went well, update the Client with the appropriate information based on prior request
				else{
					System.out.println("Request was good");
					received_packet = handleServerResponse(received_packet);
					if (received_packet != null)
						packet_action = PSPAction.valueOf(received_packet[0]);
					else
						packet_action = PSPAction.CLIENT_RESPONSE_NEG;
					sendData = PSPPacket.create_packet(packet_action, received_packet[2], Integer.parseInt(received_packet[3]), received_packet[4], PSPPubSchedule.valueOf(received_packet[5]), received_packet[6]);
					DatagramPacket sendResponsePacket = new DatagramPacket(sendData, sendData.length, IPAddress, 9876);
					try {
						clientSocket.send(sendResponsePacket);
					}
					catch(Exception e){
						if (packet_counter < 5){
							packet_counter++;
							// clientSocket.send(sendResponsePacket); // Need to make this work
						}
						else // Need to do more
							sendResponsePacket = null;
					}
					packet_counter = 0;
					if (sendResponsePacket == null){
						System.out.println("Client could not send response to Server");
					}
					
				}
			}
				
		}
		
		System.out.println("Connection terminated");
		clientSocket.close();		
		
	}
	
	// A function that processes the Server response packet and updates the Client state.
	// It also generates the 
	private static String[] handleServerResponse(String[] received_packet) throws Exception {
		byte [] receiveData = new byte[1024];
		DatagramSocket clientSocket = new DatagramSocket();
		
		// First determine what type of action we are dealing with
		// If the client has subscribed to the Channel, then create an entry for holding the events
		if (PSPAction.valueOf(received_packet[0]) == PSPAction.SERVER_SUBSCRIBE_CHANNEL){
			// A new channel was created
			channel_map.put(received_packet[4], new ArrayList<PSPEvent>());			
		}
		
		// Shortcut - listen for notifications
		//DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
		//clientSocket.receive(receivePacket);
			
		return received_packet;		
	}

	// Decodes the packet and returns an ArrayList of the sections
	private static String[] decodePacket(DatagramPacket receive_packet){		
		String receive_message = new String(receive_packet.getData()).trim();
		String[] packet_data;
		try{
			 packet_data = receive_message.split(Globals.data_separator);
		}
		catch(Exception e){
			packet_data = null;
		}
					
		if (packet_data == null){
			// Error parsing the packet.  Return the null string
			return null;
		}
		
		return packet_data;
	}
	 
	private static void handleActions(String[] packet_data){
		
	}
	
	// Parses the user's input into a String array
	private static ArrayList<String> parseInput(String input){		
		ArrayList<String> parse_string = new ArrayList<String>();
		if ((input == null) || (input.length() < 1))
			return null;		
		try{
			//parse_string = input.split("[\\s+ ;]");
			
			String simpleToken = "[^.;?!\\s\"]+";  
			  
	        String quotedToken =  
	                "(?x)             # enable inline comments and ignore white spaces in the regex         \n" +  
	                "\"               # match a double quote                                                \n" +  
	                "(                # open group 1                                                        \n" +  
	                "  \\\\.          #   match a backslash followed by any char (other than line breaks)   \n" +  
	                "  |              #   OR                                                                \n" +  
	                "  [^\\\\\r\n\"]  #   any character other than a backslash, line breaks or double quote \n" +  
	                ")                # close group 1                                                       \n" +  
	                "*                # repeat group 1 zero or more times                                   \n" +  
	                "\"               # match a double quote                                                \n";  
	  
	        String regex = quotedToken + "|" + simpleToken;  
	  
	        Matcher m = Pattern.compile(regex).matcher(input);
	  	        
	        int counter = 0;
	        
	        while(m.find()) {  
	            //parse_string.add(m.group().replace('\"', ' ').trim());	         
	        	parse_string.add(m.group());
	        }  
	        
		}catch (Exception e){
			parse_string.add("Bad"); // Returns a single-entity array saying string is "Bad"
			return parse_string;
		}
		
		return parse_string;
	}
		
}
